/**
 *  Copyright 2002-2009 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package foo.bar.blog.web.action.general;

import com.google.code.kaptcha.Constants;
import foo.bar.blog.core.domain.*;
import foo.bar.blog.core.services.BlogService;
import foo.bar.blog.radeox.DecoratedEntryTransformer;
import foo.bar.blog.radeox.BlogRenderEngine;
import foo.bar.radeox.api.engine.RenderEngine;
import foo.bar.radeox.engine.context.BaseRenderContext;
import foo.bar.blog.core.services.exception.BlogThemeNameAware;
import foo.bar.blog.web.action.Calendar;
import foo.bar.blog.web.action.ExtendedPaginatedList;
import foo.bar.blog.web.action.PaginatedBaseActionBean;
import foo.bar.blog.web.extension.*;
import foo.bar.blog.web.nonextension.HtmlResolution;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.controller.LifecycleStage;
import net.sourceforge.stripes.integration.spring.SpringBean;
import net.sourceforge.stripes.validation.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.perf4j.aop.Profiled;

import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashMap;

/**
 * Blog relation actions.
 *
 * @author tmjee
 * @version $Date: 2009-07-05 10:12:59 +0000 (Sun, 05 Jul 2009) $ $Id$
 */
public class BlogActionBean extends PaginatedBaseActionBean implements BlogThemeNameAware {

    private static final Log LOG = LogFactory.getLog(BlogActionBean.class);


    public static final String VIEW = "blog.jsp";
    public static final String VIEW_ENTRY = "entry.jsp";

    @Validate(converter = TagTypeConverter.class, on = {"view"})
    private Tag selectedTag;

    @Validate(converter= CalendarTypeConverter.class, on={"view"})
    private Calendar calendar;

    private ExtendedPaginatedList entries;

    @Validate(converter = BlogTypeConverter.class, on={"view"})
    private Blog blog;

    @Validate(converter = EntryTypeConverter.class, on={"viewEntry","saveEntryComment", "previewComment"})
    private Entry entry;

    private String comment;
    private String renderedComment;


    private String captcha;

    

    @Validate(converter = CommentTypeConverter.class, on={"saveEntryComment"})
    @ValidateNestedProperties({
        @Validate(field = "entry", converter = EntryTypeConverter.class, on={"saveEntryComment"}),
        @Validate(field = "content", trim = true, required = true, on={"saveEntryComment"})
    })
    private Comment entryComment;


    private List<Tag> allTags;

    private List<Link> allLinks;

    private String search;

    private BlogService blogService;
    private RenderEngine renderEngine;


    /**
     * After bindingAndValidation, on Stripes "view" event, we want to identified the action that user did to view
     * the blog, it's either :-
     *
     * - through search
     * - through cliking a tag
     * - through clicking a date in the calendar
     * - just normal viewing
     *
     * It identifies it through the following request parameter passed in :-
     *  - search
     *  - selectedTag
     *  - calendar
     *
     */
    @After(stages={LifecycleStage.BindingAndValidation}, on="view")
    public void after_() {
        // searching
        if (search != null) {
            setSessionAttribute(getActionId()+"_search", search);
            selectedTag = null;
            setSessionAttribute(getActionId()+"_selectedTag", null);
            calendar = null;
            setSessionAttribute(getActionId()+"_calendar", null);
        }
        else {
            search = (String) getSessionAttribute(getActionId()+"_search");
        }


        // calendaring
        if (calendar != null) {
            setSessionAttribute(getActionId()+"_calendar", calendar);
            selectedTag = null;
            setSessionAttribute(getActionId()+"_selectedTag", null);
            search = null;
            setSessionAttribute(getActionId()+"_search", null);
        }
        else {
            calendar = (Calendar) getSessionAttribute(getActionId()+"_calendar");
        }


        // taging
        if (selectedTag != null) {
            if (selectedTag.getId() <= 0) {
                selectedTag = (Tag) getSessionAttribute(getActionId()+"_selectedTag");
            }
            else {
                setSessionAttribute(getActionId()+"_selectedTag", selectedTag);
                calendar = null;
                setSessionAttribute(getActionId()+"_calendar", null);
                search = null;
                setSessionAttribute(getActionId()+"_search", null);
            }
        }
    }


    /**
     * Before rendering the view, regardless of the Strpes events, we want to decorate the
     * {@link foo.bar.blog.core.domain.Entry} to a {@link foo.bar.blog.radeox.DecoratedEntry} such that the
     * {@link foo.bar.radeox.api.engine.RenderEngine} recongnizable snippets/blocks are rendered into
     * appropriate html.
     */
    @Before(stages={LifecycleStage.ResolutionExecution})
    public void before_() {
        if (entries != null) {
            entries.setList(transform(entries.getList()));
        }
        if (entry != null) {
            entry = transform(entry);
        }
    }

    private List<Entry> transform(List<Entry> entries) {
        List<Entry> result = new ArrayList<Entry>();
        for (Entry entry: entries) {
            result.add(transform(entry));
        }
        return result;
    }

    private Entry transform(Entry entry) {
        return new DecoratedEntryTransformer(renderEngine).transform(entry);
    }


    public String getCaptcha() {
        return captcha;
    }

    public void setCaptcha(String captcha) {
        this.captcha = captcha;
    }


    /**
     * Display the entries listing page for this blog
     *
     * Input - blog, search (optional), selectedTag (optional), calendar (optional)
     * Output - entries
     *
     * @return {@link net.sourceforge.stripes.action.Resolution}
     */
    @DefaultHandler
    @Profiled(tag="BlogActionBean_view", logFailuresSeparately = true)
    public Resolution view() {
        if (search != null) {
            entries = transformPaginatedList(blogService.searchPublishedEntries(blog, search, getPaginatedList("blog_entries_search")));
        }
        else if (selectedTag != null) {
            entries = transformPaginatedList(blogService.getPublishedTaggedEntries(blog, selectedTag, getPaginatedList("blog_entries")));
        }
        else if (calendar != null) {
            entries = transformPaginatedList(blogService.getCalendaredEntries(blog, calendar, getPaginatedList("blog_entries_calendared")));
        }
        else {
            entries = transformPaginatedList(blogService.getPublishedEntries(blog, getPaginatedList("blog_entries")));
        }
        return new ForwardResolution(generateBlogThemePath(VIEW));
    }

    /**
     * Load all the tags, no resolution returned, used by <stripes:actionBean ... /> tag
     * Input - blog
     * Output - allTags
     *
     * @return {@link net.sourceforge.stripes.action.Resolution}
     */
    public Resolution allTags() {
        allTags = blogService.getAllTags(blog);
        return null;
    }

    /**
     * Load all the links, no resolution returned, used by <stripes:actionBean ... /> tag
     * Input - blog
     * Output - allLinks
     * @return
     */
    public Resolution allLinks() {
        allLinks = blogService.getAllLinks(blog);
        return null;
    }


    /**
     * Loads the blog entries listing page.
     * Input - entry
     * Output - blog, entry
     * @return
     */
    @Profiled(tag="BlogActionBean_viewEntry", logFailuresSeparately = true)
    public Resolution viewEntry() {
        blog = entry.getBlog();
        return new ForwardResolution(generateBlogThemePath(VIEW_ENTRY));
    }


    /**
     * Save the blog entry comment and redirect to that blog entry page
     * Input - entry, comment
     * Output - blog, entry, comment
     * @return
     */
    @Profiled(tag="BlogActionBean_saveEntryComment", logFailuresSeparately = true)
    public Resolution saveEntryComment() {
        blog = entry.getBlog();
        entryComment.setEntry(entry);
        blogService.saveBlogEntryComment(entryComment);
        //return new ForwardResolution(generateBlogThemePath(VIEW_ENTRY));
        return new RedirectResolution(BlogActionBean.class, "viewEntry")
                    .addParameter("entry", entry.getId())
                    .addParameter("blog", blog.getId());
    }
    @ValidationMethod(on={"saveEntryComment"},when= ValidationState.DEFAULT)
    public void validate_saveEntryComment(ValidationErrors validationErrors) {
        String _captcha = (String) getSessionAttribute(Constants.KAPTCHA_SESSION_KEY);
        if (!_captcha.equals(captcha)) {
            validationErrors.add("captcha", new SimpleError("Captcha value doesn't match"));    
        }
    }


    /**
     * Reload a portion of the page to show the comment in preview form.
     * Input - comment, entry
     * Output - renderedComment
     * @return
     */
    @Profiled(tag="BlogActionBean_previewComment", logFailuresSeparately = true)
    public Resolution previewComment() {
        BaseRenderContext context = new BaseRenderContext();
        context.setParameters(new LinkedHashMap(){
            {
                put(BlogRenderEngine.RENDER_CONTEXT_ENTRY_ID_PARAM_KEY, entry.getId());
            }
        });
        renderedComment = renderEngine.render(comment, context);
        return new HtmlResolution(renderedComment);
    }






    private ExtendedPaginatedList transformPaginatedList(ExtendedPaginatedList paginatedList) {
        paginatedList.setList(transform(paginatedList.getList()));
        return paginatedList;
    }



    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Tag getSelectedTag() {
        return selectedTag;
    }

    public void setSelectedTag(Tag selectedTag) {
        this.selectedTag = selectedTag;
    }

    public ExtendedPaginatedList getEntries() {
        return entries;
    }

    public void setEntries(ExtendedPaginatedList entries) {
        this.entries = entries;
    }

    public Blog getBlog() {
        return blog;
    }

    public void setBlog(Blog blog) {
        this.blog = blog;
    }

    public Entry getEntry() {
        return entry;
    }

    public void setEntry(Entry entry) {
        this.entry = entry;
    }

    public List<Tag> getAllTags() {
        return allTags;
    }

    public void setAllTags(List<Tag> allTags) {
        this.allTags = allTags;
    }

    public List<Link> getAllLinks() {
        return allLinks;
    }

    public void setAllLinks(List<Link> allLinks) {
        this.allLinks = allLinks;
    }


    public void setCalendar(Calendar calendar) {
        this.calendar = calendar;
    }

    public Calendar getCalendar() {
        return calendar;
    }

    public void setSearch(String search) {
        this.search = search;
    }

    public String getSearch() {
        return search;
    }

    public Comment getEntryComment() {
        return entryComment;
    }

    public void setEntryComment(Comment entryComment) {
        this.entryComment = entryComment;
    }

    @SpringBean("blogService")
    public void setBlogService(BlogService blogService) {
        this.blogService = blogService;
    }


    @SpringBean("renderEngine")
    public void setRenderEngine(RenderEngine renderEngine) {
        this.renderEngine = renderEngine;
    }

    public String getBlogThemeName() {
        if ((blog != null) && (blog.getId() > 0)) {
            return blog.getBlogThemeName();
        }
        return null;
    }
}
